<?php

namespace seecms\lib;

use seecms\See;
use seecms\SeeException;
use magein\utils\Result;

class Backup
{

    /**
     * 邮件发送结果:未发送
     */
    const SEND_RESULT_PADDING = 0;

    /**
     * 邮件发送结果:成功
     */
    const SEND_RESULT_SUCCESS = 1;

    /**
     * 邮件发送结果:失败
     */
    const SEND_RESULT_FAIL = -1;

    /**
     * 要备份的数据表名称
     * @var array
     */
    protected $tables = [];

    /**
     * 接受备份文件的邮箱地址
     * @var mixed|string
     */
    protected $receive_email = '';

    /**
     * 备份文件中数据的长度
     * @var int
     */
    protected $length = 2000;

    /**
     * @var Mailer
     */
    protected $mailer = null;

    /**
     * @param string|int|null $value
     * @return string|array
     */
    public static function translateSendResult($value = null)
    {
        $data = [
            self::SEND_RESULT_PADDING => 'Not sent',
            self::SEND_RESULT_SUCCESS => 'Successfully',
            self::SEND_RESULT_FAIL => 'Failed'
        ];

        if ($value !== null) {
            return $data[$value] ?? '';
        }

        return $data;
    }

    /**
     * 设置接受备份文件的邮箱
     * @param string|array $receive_email
     * @return void
     */
    public function setReceiveEmail($receive_email)
    {
        if (is_string($receive_email)) {
            $receive_email = explode(',', $receive_email);
        }

        $this->receive_email = $receive_email;
    }

    /**
     * @param Mailer $mailer
     * @return void
     */
    public function setMailer(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    /**
     * @param array|string $tables
     */
    public function setTables($tables): void
    {
        if (is_string($tables)) {
            $tables = explode(',', $tables);
        }

        $this->tables = $tables;
    }

    /**
     * 执行备份
     * @param array $config
     * @return Result
     */
    public function run(array $config = []): Result
    {
        if (empty($config)) {
            $config = See::provider()->general->backup;
        }

        $memory_limit = $config['memory_limit'] ?? '128M';
        ini_set('memory_limit', $memory_limit);


        if (empty($this->tables)) {
            $this->setTables(See::config()->backup->tables());
        }

        if (empty($this->receive_email)) {
            $this->setReceiveEmail($config['receive_email'] ?? []);
        }

        $this->length = intval($config['length'] ?? 0);
        // 是否压缩 0 不压缩  1 压缩
        $zip = $config['zip'] ?? 0;

        if ($this->length > 5000) {
            $this->length = 5000;
        }

        if ($this->length <= 500) {
            $this->length = 500;
        }

        $data = [];
        try {
            // 创建表sql文件
            foreach ($this->tables as $table) {
                $res = $this->makeSqlFile($table);
                if (!$res->getCode()) {
                    $data[] = $table;
                    // 加入到记录表中
                    See::table()->database_backup->save($res->getData());
                }
            }
        } catch (\Exception $exception) {
            return Result::error($exception->getMessage());
        }

        $data = See::table()->database_backup->select();

        $this->sendMail($data, $zip);

        return Result::success(null, 'Successfully');
    }

    /**
     * 发送邮件
     * @param $data
     * @param bool $zip
     * @return Result
     */
    public function sendMail($data, bool $zip = false): Result
    {
        if (!$this->mailer instanceof Mailer) {
            return Result::error('Please set up a mailer instance');
        }

        if (isset($data['filepath'])) {
            $data = [$data];
        }

        $send = function ($data, $attachment = null) {

            if (empty($attachment)) {
                $root = See::config()->backup->root();
                $real_path = $root . '/' . trim($data['filepath'] ?? '');
                if (is_file($real_path)) {
                    $attachment = $real_path;
                }
            }

            try {
                $this->mailer->addAttachment($attachment);
            } catch (\Exception $exception) {

            }

            $backup_at = $data['created_at'] ?? '';
            $filesize = Transfer::filesizeFormatter(filesize($attachment));
            $table = $data['name'] ?? '';
            $filename = pathinfo($attachment, PATHINFO_BASENAME);

            $begin = $data['begin'] ?? '';
            $end = $data['end'] ?? '';
            $increase = $data['increase'] ?? '';

            $simple = '';
            if ($backup_at) {
                $simple .= "<p>Backup at：$backup_at</p>";
            }

            if ($begin) {
                $simple .= "<p>Begin：$begin</p>";
            }

            if ($end) {
                $simple .= "<p>end：$end</p>";
            }

            if ($increase) {
                $simple .= "<p>Iincrease：$increase</p>";
            }


            $html = <<<EOF
<h2>Backup Info：</h2>
<p>table name：{$table}</p>
{$simple}
<p>Filename：$filename</p>
<p>Filesize：$filesize</p>
EOF;

            return $this->mailer->html($this->receive_email, ' backup file', $html);
        };

        if ($zip) {
            $name = '';
            foreach ($data as $item) {
                $name .= $item['name'] . ',';
            }
            $name = trim($name, ',');
            // 打包发送
            $attachment = $this->zip($data);
            $send(compact('name'), $attachment);
        } else {
            foreach ($data as $item) {
                $result = $send($item);
                See::table()->database_backup->save([
                    'id' => $item['id'],
                    'send_at' => date('Y-m-d H:i:s'),
                    'send_result' => $result->getCode() ? self::SEND_RESULT_FAIL : self::SEND_RESULT_SUCCESS,
                    'send_fail_reason' => $result->getCode() ? $result->getMsg() : ''
                ]);
            }
        }

        return Result::success(null, 'Successfully');
    }

    /**
     * 压缩文件
     * @param array $data
     * @return Result|string
     * @throws SeeException
     */
    public function zip(array $data)
    {
        if (empty($data)) {
            return Result::error(Operate::notFoundData());
        }

        if (!extension_loaded('zip')) {
            return Result::error('Zip extension not installed yet');
        }

        $root = See::config()->backup->root();
        $zip_path = See::config()->backup->zipPath();

        $save_path = ck_directory($root, $zip_path);
        $zip_filename = date('YmdHis') . '.zip';
        $target = $save_path . $zip_filename;

        $zip = new \ZipArchive();
        $zip->open($target, \ZipArchive::CREATE);

        foreach ($data as $item) {
            $real_path = $root . '/' . trim($item['filepath']);
            $filename = pathinfo($real_path, PATHINFO_BASENAME);
            if (is_file($real_path)) {
                $zip->addFromString($filename, file_get_contents($real_path));
            }
        }
        $zip->close();

        if (!is_file($target)) {
            throw new SeeException($zip_path . ' empty');
        }

        return $target;
    }

    /**
     * 创建sql语句文件
     * @param $table_name
     * @return Result
     * @throws SeeException
     */
    public function makeSqlFile($table_name): Result
    {
        $config = See::config()->backup;
        $root = $config->root();
        $backup_path = $config->backupPath();
        $save_path = ck_directory($root, $backup_path);

        $log = See::table()->database_backup->where('name', $table_name)->order('id')->find();

        $begin_id = 0;
        $end_id = 0;
        $insert_sql = '';
        $increase = 0;

        if ($log) {
            $begin_id = $log['end_id'];
        }

        $where = [
            ['id', '>', $begin_id],
        ];

        See::db()->table($table_name)->where($where)->chunk($this->length, function ($records) use ($table_name, &$end_id, &$insert_sql, &$increase) {
            if ($records) {
                foreach ($records as $record) {
                    $fields = '';
                    $values = '';
                    foreach ($record as $field => $value) {
                        if (preg_match('/^\d+$/', $value . '')) {
                            $value = intval($value);
                        } elseif (is_null($value)) {
                            $value = 'NUll';
                        } else {
                            $value = "'$value'";
                        }
                        $fields .= "`$field`,";
                        $values .= "$value,";
                    }
                    $increase += 1;
                    $fields = trim($fields, ',');
                    $values = trim($values, ',');
                    $insert_sql .= sprintf('INSERT INTO `%s`(%s) VALUES (%s)', $table_name, $fields, $values) . "\n";
                    $end_id = $record['id'];
                }
            }
        });

        $data = compact('begin_id', 'end_id', 'increase');

        if (empty($increase)) {
            return Result::error($table_name . ' increase null');
        }

        $filename = $table_name . '_' . $data['begin_id'] . '_' . $end_id . '.sql';
        $filepath = $save_path . $filename;
        if (is_file($filename)) {
            return Result::error('The backup file already exists' . $filename);
        }

        file_put_contents($filepath, $insert_sql);
        if (is_file($filepath)) {
            $data['name'] = $table_name;
            $data['filepath'] = $backup_path . '/' . $filename;
            $data['filesize'] = filesize($filepath);
        }

        return Result::success($data);
    }
}